package com.qfedu.edu.seckill.payservice.service.impl;

import com.alibaba.druid.util.StringUtils;
import com.alibaba.fastjson.JSON;
import com.qfedu.edu.seckill.bo.OrderShow;
import com.qfedu.edu.seckill.bo.QueueMessage;
import com.qfedu.edu.seckill.bo.ReduceStock;
import com.qfedu.edu.seckill.constance.RabbitMQConstance;
import com.qfedu.edu.seckill.constance.RedisConstance;
import com.qfedu.edu.seckill.exception.BusinessException;
import com.qfedu.edu.seckill.manager.RedisManager;
import com.qfedu.edu.seckill.payservice.enum1.PayStatus;
import com.qfedu.edu.seckill.payservice.service.IPayService;
import com.qfedu.edu.seckill.payservice.utils.PayCommonUtil;
import com.qfedu.edu.seckill.payservice.utils.PayConfigUtil;
import com.qfedu.edu.seckill.payservice.utils.XMLUtil;
import com.qfedu.edu.seckill.result.ResponseCode;
import com.qfedu.edu.seckill.vo.PayVo;
import lombok.extern.slf4j.Slf4j;
import org.jdom.JDOMException;
import org.springframework.amqp.core.Message;
import org.springframework.amqp.core.MessageProperties;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.*;
import java.util.*;
import java.util.concurrent.TimeUnit;

/**
 * @author xiaobobo
 * @title: payService
 * @projectName cd-java-2404-seckill-parent
 * @description: TODO
 * @date 2025/2/7  9:31
 */
@Service
@Slf4j
public class PayService implements IPayService {

    @Autowired
    private RedisManager redisManager;

    @Autowired
    private RabbitTemplate rabbitTemplate;

    @Override
    public String pay(PayVo payVo) throws Exception {
        //首先是参数校验
        checkParam(payVo);
        //执行到这里 参数无误 那么就需要分类来支付
        switch (payVo.getType()){
            case 0:
                //微信支付
                return weixinPay(payVo);
            case 1:
                //支付宝支付
                return aliPay(payVo);
            case 2:
                //银联支付
                return unionPay(payVo);
            default:
                throw new BusinessException(ResponseCode.PARAM_EXCEPTION);
        }
    }

    @Override
    public void weixinPayNotify(HttpServletRequest request, HttpServletResponse response) throws IOException, JDOMException {
        //读取数据 弄成map对象
        Map<String, String> m = readDataToMap(request);
        //将读取到的map弄成TreeMap方便后续的签名校验
        SortedMap<Object, Object> packageParams = getObjectObjectSortedMap(m);
        // 账号信息
        String key = PayConfigUtil.API_KEY; // key
        //这里获取到的就是咋们的订单号
        String out_trade_no = (String) packageParams.get("out_trade_no");//订单号,实际开发中应该在下⾯的 IF 中, 除⾮需要对每个订单的每次⽀付结果做记录
        //判断签名是否正确
        if (PayCommonUtil.isTenpaySign("UTF-8", packageParams, key)) {
            //------------------------------
            //处理业务开始
            //------------------------------
            String resXml = "";
            if ("SUCCESS".equals((String) packageParams.get("result_code"))) {
                //成功了要做什么业务处理呢?
                updateOrderStatus(out_trade_no,PayStatus.SUCCESS);
                //修改用户的购买状态
                updateSellStatus(out_trade_no,"success");

                //下面这两个操作也存在高并发(只能去MQ中完成)
                //TODO 2、修改订单状态(MySQL)(这里就出现了MQ中一个重要的特性：队列消息的乱序)
                //队列消息的乱序 如何解决呢?  1、单个MQ机器的时候 我们可以将 这个队列消息 放到订单队列中去
                //这样就可以利用这个队列本身拥有顺序的这个特性解决这个问题
                //解决方案2：在修改订单状态的时候 先查询订单是否存在 存在 那么修改状态  不存在 重回队列(等待)
                updateOrderMySQLStatus(out_trade_no,1);
                //TODO 3、在数据库中真正的实现减库存
                reduceMySQLStock(out_trade_no);
                //通知微信.异步确认成功.必写.不然会⼀直通知后台.⼋次之后就认为交易失败了.
                resXml = "<xml>" + "<return_code><![CDATA[SUCCESS]]> </return_code > "
                        + "<return_msg><![CDATA[OK]]></return_msg>" + "</xml> ";
            } else {   //支付失败
                // 1、需要记录支付成功的状态  使用redis来做  pay:result:订单号  0(正在支付)/1(成功)/2(失败)
                //成功了要做什么业务处理呢?
                updateOrderStatus(out_trade_no,PayStatus.FAIL);
                //4、用户的秒杀状态
                //修改用户的购买状态
                updateSellStatus(out_trade_no,"fail");
                //TODO 2、修改订单状态(MySQL)
                updateOrderMySQLStatus(out_trade_no,2);
                //TODO 3、库存还需要还原(Redis中)
                 resumeStock(out_trade_no);
                resXml = "<xml>" + "<return_code><![CDATA[FAIL]]></return_code>"
                        + "<return_msg><![CDATA[报⽂为空]]></return_msg>" + "</xml> ";
            }
            BufferedOutputStream out = new BufferedOutputStream(
                    response.getOutputStream());
            out.write(resXml.getBytes());
            out.flush();
            out.close();
        } else {
            log.info("订单{}签名校验失败",out_trade_no);
        }
    }

    @Override
    public String getPayStatus(String orderNo) {
        if(StringUtils.isEmpty(orderNo)){
            throw new BusinessException(ResponseCode.PARAM_EXCEPTION);
        }
        //执行到这里说明无误
        //构建这个支付状态的这个key
        String payResultKey= RedisConstance.getPayResultPrefix()+orderNo;
        //去Redis中读取数据
        String status = redisManager.getString(payResultKey);
        if(StringUtils.isEmpty(status)){
            return "noResult";
        }
        return status;
    }

    /**
     * 在MySQL中实现减库存
     * @param outTradeNo
     */
    private void reduceMySQLStock(String outTradeNo) throws UnsupportedEncodingException {
        ReduceStock reduceStock = new ReduceStock();
        reduceStock.setNum(1);
        reduceStock.setOrderNumber(outTradeNo);

        //这里也需要封装对象
        QueueMessage queueMessage = new QueueMessage();
        queueMessage.setQueueName(RabbitMQConstance.getReduceStockQueue());
        queueMessage.setType(3);
        queueMessage.setMessage(JSON.toJSONString(reduceStock));
        //将这个消息转换成JSON格式
        String msg = JSON.toJSONString(queueMessage);
        //最终将这个消息发送到指定的队列中去
        rabbitTemplate.send(RabbitMQConstance.getReduceStockQueue(),new Message(msg.getBytes("UTF-8"),new MessageProperties()));
        log.info("减库存发送消息到队列成功....");
    }

    /**
     * 修改MySQL中的订单状态
     * @param outTradeNo
     * @param status
     */
    private void updateOrderMySQLStatus(String outTradeNo, int status) throws UnsupportedEncodingException {

        OrderShow orderShow = new OrderShow();
        orderShow.setOrderNum(outTradeNo);
        orderShow.setStatus(status);

        //封装队列消息
        QueueMessage queueMessage = new QueueMessage();
        queueMessage.setMessage(JSON.toJSONString(orderShow));
        //设置队列的名字
        queueMessage.setQueueName(RabbitMQConstance.getOrderQueue());
        //设置类型
        queueMessage.setType(2);
        //将这个消息对象弄成String类型
        String message = JSON.toJSONString(queueMessage);
        rabbitTemplate.send(
                RabbitMQConstance.getOrderQueue(),
                new Message(message.getBytes("UTF-8"),
                        new MessageProperties()));
        log.info("修改订单状态发送消息到队列成功....");
    }

    /**
     * 还原Redis中的这个库存(就是放行的计数器-1)
     *  减库存的计数器-1
     * @param outTradeNo
     */
    private void resumeStock(String outTradeNo) {
        OrderShow orderShow = getOrderShow(outTradeNo);
        String key = RedisConstance.getCountSellPrefix() + orderShow.getClassId();
        redisManager.decr(key);
        //减库存的这个计数-1
        String reduceStockKey= RedisConstance.getReduceStockPrefix()+orderShow.getClassId();
        //进行计数
        redisManager.decr(reduceStockKey);
    }

    /**
     * 修改用户的购买状态
     * @param out_trade_no
     */
    private void updateSellStatus(String out_trade_no,String status) {
        //TODO 4、用户的秒杀状态
        OrderShow orderShow = getOrderShow(out_trade_no);
        //首先构建用户秒杀状态的这个key
        String userSellStatusKey = "sell:status:" + orderShow.getUserId() + ":" + orderShow.getClassId();
        //修改这个购买的状态
        redisManager.addString(userSellStatusKey,status,30,TimeUnit.DAYS);
    }

    /**
     * 通过订单号获取订单对象
     * @param out_trade_no
     * @return
     */
    private OrderShow getOrderShow(String out_trade_no) {
        //这里首先要通过订单号去查询这个订单信息 找到classId以及userId
        String orderInfo = redisManager.getString(out_trade_no);
        //将订单信息转换成对象
        OrderShow orderShow = JSON.parseObject(orderInfo, OrderShow.class);
        return orderShow;
    }

    /**
     * 修改订单的状态
     * @param out_trade_no
     * @param payStatus
     */
    private void updateOrderStatus(String out_trade_no,PayStatus payStatus) {
        // TODO 1、需要记录支付成功的状态  使用redis来做  pay:result:订单号  0(正在支付)/1(成功)/2(失败)
        //构建支付结果的key
        String payResultKey= RedisConstance.getPayResultPrefix()+ out_trade_no;
        redisManager.addString(payResultKey, payStatus.name(),30, TimeUnit.DAYS);
    }

    private static SortedMap<Object, Object> getObjectObjectSortedMap(Map<String, String> m) {
        //过滤空 设置 TreeMap
        SortedMap<Object, Object> packageParams = new TreeMap<Object, Object>();
        Iterator it = m.keySet().iterator();
        while (it.hasNext()) {
            String parameter = (String) it.next();
            String parameterValue = m.get(parameter);
            String v = "";
            if (null != parameterValue) {
                v = parameterValue.trim();
            }
            packageParams.put(parameter, v);
        }
        return packageParams;
    }

    /**
     * 读取微信服务器传递过来的数据 弄成map对象 方便后续数据的使用....
     * @param request
     * @return
     * @throws IOException
     */
    private static Map<String, String> readDataToMap(HttpServletRequest request) throws IOException, JDOMException {
        //这里是从请求中获取腾讯服务器 传输过来的参数
        InputStream inputStream;
        StringBuffer sb = new StringBuffer();
        inputStream = request.getInputStream();
        String s;
        BufferedReader in = new BufferedReader(
                new InputStreamReader(inputStream,
                        "UTF-8"));
        while ((s = in.readLine()) != null) {
            sb.append(s);
        }
        in.close();
        inputStream.close();

        //解析xml成map
        Map<String, String> m = new HashMap<String, String>();
        m = XMLUtil.doXMLParse(sb.toString());
        return m;
    }

    /**
     * 银联支付要执行的方法
     * @param payVo
     * @return
     */
    private String unionPay(PayVo payVo) {
        return null;
    }

    /**
     * 支付宝支付要执行的方法
     * @param payVo
     * @return
     */
    private String aliPay(PayVo payVo) {

        return null;
    }

    /**
     * 微信支付要执行的方法
     * @param payVo
     * @return
     */
    private String weixinPay(PayVo payVo) throws Exception {
        //下面这个方法中
        //最终返回了一个 url信息  这个url就是我们的二维码的信息
        String url = PayCommonUtil.weixin_pay(payVo.getPrice(), payVo.getClassName(), payVo.getOrderNumber());
        return url;
    }

    /**
     * 参数校验
     * @param payVo
     */
    private void checkParam(PayVo payVo) {
        if(null==payVo|| StringUtils.isEmpty(payVo.getPrice())||StringUtils.isEmpty(payVo.getClassName())||StringUtils.isEmpty(payVo.getOrderNumber())||null==payVo.getType()){
          throw new BusinessException(ResponseCode.PARAM_EXCEPTION);
        }
    }
}
